import json
import logging

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError, HTTPException
from fastapi.responses import JSONResponse
from starlette.status import HTTP_401_UNAUTHORIZED

from common.exceptions import BizException, ErrorCode
from common.request import safe_extract_body
from common.response import ApiResponse

logger = logging.getLogger(__name__)


def register_exception_handlers(app: FastAPI) -> None:
	"""注册全局异常处理器（由 main.py 调用）"""

	@app.exception_handler(BizException)
	async def biz_exception_handler(request: Request, exc: BizException):
		"""BizException 统一处理"""
		audit_id = getattr(request.state, "audit_id", None)

		# 公共提取请求体
		from app.crud.audit_log_crud import create_audit_log_with_body
		from common.database import get_db_dependency

		# 使用同步方式获取数据库连接
		db_gen = get_db_dependency()
		try:
			db = next(db_gen)  # 同步获取数据库连接
			body = await safe_extract_body(request)

			# ---------- 1. 已存在审计记录 → 只更新 ----------
			if audit_id:
				# 更新结果及请求体（可选）
				db.update(
					"UPDATE base_audit_log SET result = 0, request_body = %s WHERE id = %s",
					(json.dumps(body, ensure_ascii=False) if body else None, audit_id)
				)
				return JSONResponse(
					status_code=exc.status_code,
					content=ApiResponse.error(
						message=exc.message,
						code=exc.status_code,
						error_code=exc.biz_code,
						details=exc.details
					).model_dump(),
					headers=exc.headers,
				)

			# ---------- 2. 无审计记录 → 补写失败日志 ----------
			user_code = None
			try:
				from app.common.auth import get_current_user
				user = await get_current_user(
					token=request.headers.get("Authorization", "").replace("Bearer ", ""),
					db=db,
				)
				user_code = user.user_code
			except Exception:
				pass

			await create_audit_log_with_body(
				db=db,
				user_code=user_code,
				action=exc.error_code.biz_code,  # 如 "UPDATE_ROLE"
				target_type="API",
				target_id=request.url.path,
				request_ip=request.client.host or "unknown",
				request_uri=str(request.url),
				request_body=body,
				result=False,
			)
		except Exception as log_exc:
			logger.warning("audit log on exception failed: %s", log_exc)
		finally:
			# 使用 close() 而不是 aclose()
			try:
				db_gen.close()  # 关闭生成器，释放资源
			except Exception:
				pass

		return JSONResponse(
			status_code=exc.status_code,
			content=ApiResponse.error(
				message=exc.message,
				code=exc.status_code,
				error_code=exc.biz_code,
				details=exc.details
			).model_dump(),
			headers=exc.headers,
		)

	@app.exception_handler(RequestValidationError)
	async def validation_exception_handler(request: Request, exc: RequestValidationError):
		"""参数校验失败 → 统一格式"""
		error = exc.errors()[0]
		field = ".".join(map(str, error["loc"]))
		msg = f"参数错误: {field} {error['msg']}"
		return JSONResponse(
			status_code=400,
			content=ApiResponse.error(
				message=msg,
				code=400,
				error_code=ErrorCode.BAD_REQUEST.name,
				details={field: error["msg"]}
			).model_dump()
		)

	@app.exception_handler(HTTPException)
	async def http_exception_handler(request: Request, exc: HTTPException):
		"""统一处理 FastAPI 原生 HTTPException（含 401）"""
		if exc.status_code == HTTP_401_UNAUTHORIZED:
			message = (
				"未提供或无效的认证信息"
				if exc.detail == "Not authenticated"
				else str(exc.detail)
			)
			return JSONResponse(
				status_code=HTTP_401_UNAUTHORIZED,
				content=ApiResponse.error(
					message=message,
					code=HTTP_401_UNAUTHORIZED,
					error_code=ErrorCode.UNAUTHORIZED.name
				).model_dump(),
				headers=exc.headers
			)
		# 其他 HTTPException 也统一包装
		return JSONResponse(
			status_code=exc.status_code,
			content=ApiResponse.error(
				message=str(exc.detail),
				code=exc.status_code
			).model_dump(),
			headers=exc.headers
		)

	@app.exception_handler(Exception)
	async def global_exception_handler(request: Request, exc: Exception):
		"""兜底：未捕获异常"""
		logger.exception("Unhandled exception at %s: %s", request.url, exc)
		return JSONResponse(
			status_code=500,
			content=ApiResponse.error(
				message="服务器内部错误",
				code=500,
				error_code=ErrorCode.INTERNAL_SERVER_ERROR.name
			).model_dump()
		)
